import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;

public class JdbcUtils {

  // 将每个单独的函数看懂，并做好注释
  static {
    try {
      // 使用加载类的方式来注册驱动
      Class.forName("com.mysql.jdbc.Driver");
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e.getMessage());
    }
  }

  private static final String PASSWORD = "123456789";
  private static final String URL = "jdbc:mysql://localhost:3306/test";
  private static final String USER_NAME = "root";

  /**
   * 完成数据库的Update, Delete以及Insert操作，
   *
   * @param sql Sql语句
   * @param params 需要设置的参数
   * @return 更新的数据数量
   */
  public static int update(String sql, Object... params) {
    Connection cn = null;
    PreparedStatement st = null;

    try {
      cn = getConnection();

      /*
       * PrepareStatement ps = cn.prepareStatement(sql);
       * ps.setObject(1, params[0]);
       * ...
       * ps.setObject(params.length, params[params.length - 1]);
       */
      st = getPrepareStatement(cn, sql, params);

      // 执行
      return st.executeUpdate();
    } catch (SQLException e) {
      throw new RuntimeException(e.getMessage());
    } finally {
      JdbcUtils.close(cn, st, null);
    }
  }

  /**
   * 完成数据库的查询操作
   *
   * @param cs 类的描述对象
   * @param sql Sql语句
   * @param params 需要设置的参数
   * @return 更新的数据数量
   */
  public static Object query(Class cs, String sql, Object... params) {
    Connection cn = null;
    PreparedStatement st = null;
    ResultSet rs = null;
    Object o = null;

    try {
      // 获取链接
      cn = getConnection();

      // 设置sql语句
      st = getPrepareStatement(cn, sql, params);

      // 获取查询结果
      rs = st.executeQuery();

      // 将查询结果包装成一个对象进行返回
      o = resultSetToObject(rs, cs);
    } catch (SQLException e) {
      throw new RuntimeException(e.getMessage());
    } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
      e.printStackTrace();
    } finally {
      JdbcUtils.close(cn, st, rs);
    }

    return o;
  }

  /**
   * 将ReslutSet的结果集根据cs类描述参数，封装成一个对象进行返回
   *
   * @param rs 结果集
   * @param cs 类描述对象
   * @return 数据对象
   */
  private static Object resultSetToObject(ResultSet rs, Class cs)
      throws SQLException, InvocationTargetException, IllegalAccessException, InstantiationException {
    // 调用类的默认构造函数，生成一个对象
    Object o = cs.newInstance();

    // 获取resultSet中的所有列名
    String[] names = getColumnName(rs);

    // 获取cs中的所有方法信息
    Method[] methods = cs.getMethods();

    while (rs.next()) {
      for (int i = 0; i < names.length; i++) {
        // 获取对应列名为(name[i])的值
        Object value = rs.getObject(names[i]);

        // 找到对应的方法 "set" + 大写 + 后面小写
        String methodName = "set" + names[i].substring(0, 1).toUpperCase()
            + names[i].substring(1);

        // 获取方法
        Method method = null;
        for (Method m : methods) {
          if (m.getName().equals(methodName)) {
            method = m;
          }
        }
        if (method != null) {

          // 调用o对象上的method方法，并传入value作为参数
          method.invoke(o, value);
        }
      }
    }

    return o;
  }

  /***
   * 获取ResultSet中所有的列名
   * @param rs 查询的结果集合
   * @return 所有列名组和成的数组
   * @throws SQLException
   */
  private static String[] getColumnName(ResultSet rs) throws SQLException {
    // meta data : 元数据 -> 对事物的描述
    ResultSetMetaData md = rs.getMetaData();

    // 获取列的数量
    String[] names = new String[md.getColumnCount()];

    // 通过循环，将所有的列名获取到
    for (int i = 0; i < md.getColumnCount(); i++) {
      names[i] = md.getColumnName(i + 1);
    }

    return names;
  }

  /***
   * 获取链接
   * @return 返回数据库连接对象
   * @throws SQLException
   */
  public static Connection getConnection() throws SQLException {
    return DriverManager.getConnection(URL, USER_NAME, PASSWORD);
  }

  /**
   * 将sql语句进行拼接生成
   *
   * @param cn 需要查询的链接
   * @param sql sql语句
   * @param params sql语句中的"?"对应的参数数组
   * @return 查询语句 PreparedStatement
   */
  public static PreparedStatement getPrepareStatement(Connection cn, String sql, Object... params)
      throws SQLException {
    PreparedStatement ps = cn.prepareStatement(sql);
    for (int i = 0; i < params.length; i++) {
      ps.setObject(i + 1, params[i]);
    }

    return ps;
  }

  /**
   * 关闭所有资源
   *
   * @param cn 链接
   * @param st 查询语句
   * @param rs 查询结果
   */
  public static void close(Connection cn, Statement st, ResultSet rs) {
    if (rs != null) {
      try {
        rs.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }

    if (st != null) {
      try {
        st.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }

    if (rs != null) {
      try {
        rs.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
  }
}
